package cn.simon.free.redis.mq.component;

import cn.simon.free.redis.mq.exceptions.NormalException;
import cn.simon.free.redis.mq.utils.IpUtils;
import com.google.common.base.Splitter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import static java.lang.Thread.sleep;

/**
 * 雪花算法
 * 1. 0 + 41bit时间戳 + 8bit工作机器id(5bit数据id+5bit工作id) + 12bit序列号 (使用的这种)
 * 2. 0 + 41bit时间戳 + 8bit工作机器id(3bit数据id+5bit工作id) + 2bit回拨 + 12bit序列号 (2位回拨位,用来处理时间回拨,最大值才3,意义不大)
 *
 * @author simon
 * @date 2021/7/16 11:36
 * @copyright:
 * @modifier
 * @motto a no talent, people who have to study hard
 */
@Slf4j
@Component
public class IdGeneratorSupport {

    /**
     * 初始游标时间戳(2021-07-20 11:25:17)
     */
    private static final long CURSOR_TIME = 1626751517000L;
    /**
     * 3bit数据id
     */
    private static final long DATACENTER_ID = 1L;
    /**
     * 误差值
     */
    private static final long OFFSET_TIME = 1000L;
    /**
     * 5bit工作id(可以使用主机ip,环境变量)
     * 现在使用的本机ip最后3位然后“&31”计算,有弊端也会出现重复id(ip不连续,跳跃性增加,例如:32 64 计算完成后都是0)
     * ip连续性的话没有问题
     */
    private final long workerId;
    /**
     * 12bit序列号id(最大值2^12-1  等价于  -1 ^ (-1<<12))
     */
    private long sequence = 0L;
    /**
     * 记录上次的时间
     */
    private long lastTimestamp = -1L;

    public IdGeneratorSupport() {
        //没有ip就当成单机使用
        String localIp = Splitter.on(".").splitToList(IpUtils.getLocalIpAddress()).stream().skip(3).findFirst().orElse("0");
        this.workerId = Long.parseLong(localIp) & 31;
    }

    /**
     * 获取下一个id
     *
     * @return
     */
    public synchronized Long nextId() {
        long timestamp = timeGen();
        //毫秒级最大并发数,获取当前时间戳如果等于上次时间戳(同一毫秒内),则在序列号加1;否则序列号赋值为0,从0开始。
        if (timestamp == this.lastTimestamp) {
            this.sequence = (this.sequence + 1) & 4095;
            if (this.sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }
        //时间回拨后小于1s,休眠1s,重新获取
        if (timestamp < lastTimestamp) {
            timestamp = tilNextMillis(lastTimestamp);
        }
        //将上次时间戳值刷新
        lastTimestamp = timestamp;
        return (timestamp - CURSOR_TIME) << 22 | DATACENTER_ID << 17 | this.workerId << 12 | this.sequence;
    }

    /**
     * 获取下一个id
     *
     * @return
     */
    public String nextStringId() {
        return nextId().toString();
    }

    @SneakyThrows
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            long abs = Math.abs(timestamp - lastTimestamp);
            if (abs <= OFFSET_TIME) {
                sleep(OFFSET_TIME);
                timestamp = timeGen();
            } else {
                throw new NormalException(String.format("clock is moving backwards,more than 1s.  Rejecting requests until %s milliseconds", abs));
            }
        }
        return timestamp;
    }

    /**
     * 获取系统当前的时间戳
     *
     * @return
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }
}
